package fr.bugtracker.dao.technique;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.List;

import javax.annotation.PostConstruct;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.ProjectionList;
import org.hibernate.criterion.Projections;

import fr.bugtracker.commun.dto.DtoRetourPagination;
import fr.bugtracker.commun.outils.UtilsHibernate;
import fr.bugtracker.service.AbstractService;

/**
 * Dao abstraite générique CRUD pour les entités, paramétrée par "generics".
 * @author francois.robin
 * @param <T> -
 */
public abstract class DaoAbstract<T extends IdentifiableEntity> extends AbstractService {

	/**
	 * criteria builder
	 */
	protected CriteriaBuilder builder;
	/**
	 * criteria query
	 */
	protected CriteriaQuery query;
	/**
	 * criteria root
	 */
	protected Root root;
	/**
	 * le type de la classe
	 */
	protected Class<T> entityClass;

	/**
	 * Constructeur de la classe <code>DaoAbstract</code>.
	 */
	public DaoAbstract() {
	}

	/**
	 * Méthode d'initialisation de la dao
	 */
	@PostConstruct
	private void init() {
		ParameterizedType paramType = (ParameterizedType) this.getClass().getGenericSuperclass();
		entityClass = (Class) paramType.getActualTypeArguments()[0];
	}

	/**
	 * Méthode de création d'un enregistrement
	 * @param entity T
	 */
	public void create(T entity) {
		Session session = UtilsHibernate.getSession();
		session.beginTransaction();
		session.save(entity);
		session.getTransaction().commit();
		session.flush();
	}

	/**
	 * Méthode de modification d'un enregistrement
	 * @param entity T
	 */
	public void edit(T entity) {
		Session session = UtilsHibernate.getSession();
		session.beginTransaction();

		session.saveOrUpdate(entity);

		session.getTransaction().commit();
		session.flush();
	}

	/**
	 * Méthode de suppression d'un enregistrement
	 * @param entity T
	 */
	public void remove(T entity) {
		Session session = UtilsHibernate.getSession();
		session.beginTransaction();
		session.delete(entity);
		session.getTransaction().commit();
		session.flush();
	}

	/**
	 * Méthode de recherche d'un enregistrement par rapport a son id
	 * @param id {@link Object}
	 * @return T
	 */
	public T find(Serializable id) {
		return (T) UtilsHibernate.getSession().get(entityClass, id);
	}

	/**
	 * Méthode de recherche de tous les enregistrements
	 * @return {@link List}< T >
	 */
	public List<T> findAll() {
		return UtilsHibernate.getSession().createCriteria(entityClass).list();
	}

	/**
	 * Méthode de recherche d'enregistrement dans une intervalle donnée
	 * @param range int[]
	 * @return {@link List}< T >
	 */
	public List<T> findRange(int[] range) {
		return UtilsHibernate.getSession().createCriteria(entityClass).setFirstResult(range[0]).setMaxResults(range[1] - range[0]).list();
	}

	/**
	 * Méthode pour rechercher des enregistrements avec pagination
	 * @param first int
	 * @param pageSize int
	 * @param sortField {@link String}
	 * @param sortOrder {@link String}
	 * @param criteria {@link Predicate}...
	 * @return {@link DtoRetourPagination}
	 */
	public DtoRetourPagination findByPagination(int first, int pageSize, String sortField, String sortOrder, Criteria criteria) {
		DtoRetourPagination pagination = new DtoRetourPagination();
		ProjectionList projections = Projections.projectionList().add(Projections.count("id"));
		criteria.setProjection(projections);

		pagination.setSize(((Long) criteria.uniqueResult()).intValue());

		criteria.setProjection(null);

		if (sortField != null) {
			if (sortOrder.equals("ASCENDING")) {
				criteria.addOrder(Order.asc(sortField));
			} else {
				criteria.addOrder(Order.desc(sortField));
			}
		}
		pagination.setListeObjects(criteria.setFirstResult(first).setMaxResults(pageSize).list());
		return pagination;
	}

	/**
	 * Méthode de compte de nombre d'enregistrement
	 * @return int
	 */
	public int count() {
		Criteria criteria = UtilsHibernate.getSession().createCriteria(entityClass);
		criteria.setProjection(Projections.count("id"));
		return ((Long) criteria.uniqueResult()).intValue();
	}

}
